Habiendo hecho la red neuronal fully connected en el tutorial anterior, ahora vamos a usar las capas convolutivas para clasificar el dataset MNIST haciendo uso de PyTorch.
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# Definimos las transformaciones para normalizar los datos
# Las imágenes originales están en escala de grises con valores entre 0.0 y 1.0
transform = transforms.Compose([
transforms.ToTensor(), # Convertimos imágenes a Tensores
transforms.Normalize((0.5,), (0.5,)) # Normalizamos con media 0.5 y desviación estándar 0.5
])
# Cargamos el dataset MNIST
train_data = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
test_data = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
# DataLoader para los conjuntos de entrenamiento y prueba
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False)
Definiremos una clase para nuestra red convolutiva. Esta red tendrá dos capas convolutivas seguidas de dos capas lineales (fully connected). Usaremos ReLU como nuestra función de activación y aplicaremos max pooling después de cada capa convolutiva.
import torch.nn as nn
import torch.nn.functional as F
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2) # Entrada: 1 canal, Salida: 16 canales, kernel de 5x5
self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) # Max pooling con un kernel de 2x2
self.conv2 = nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2) # Entrada: 16 canales, Salida: 32 canales
self.fc1 = nn.Linear(32 * 7 * 7, 120) # Imágenes reducidas a 7x7 después del pooling
self.fc2 = nn.Linear(120, 10) # 10 clases de salida para MNIST
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
print(x.shape)
x = self.pool(F.relu(self.conv2(x)))
print(x.shape)
x = x.view(-1, 32 * 7 * 7) # Aplanamos el tensor para la capa lineal
print(x.shape)
x = F.relu(self.fc1(x))
print(x.shape)
x = self.fc2(x)
return x
Para entrenar la red, necesitamos definir una función de pérdida y un optimizador. Usaremos la entropía cruzada como nuestra función de pérdida y el optimizador Adam.
import torch.optim as optim
model = ConvNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Entrenamiento
epochs = 5
for epoch in range(epochs):
running_loss = 0.0
for images, labels in train_loader:
optimizer.zero_grad() # Limpiamos los gradientes
outputs = model(images) # Pasamos las imágenes por la red
loss = criterion(outputs, labels) # Calculamos la pérdida
loss.backward() # Backpropagation
optimizer.step() # Actualizamos los pesos
running_loss += loss.item()
print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}')
Epoch 1, Loss: 0.17474545879207098 Epoch 2, Loss: 0.048938684453040976 Epoch 3, Loss: 0.03398002788196688 Epoch 4, Loss: 0.024567360670662234 Epoch 5, Loss: 0.020677283790541442
Después del entrenamiento, evaluamos el rendimiento del modelo en el conjunto de prueba.
correct = 0
total = 0
with torch.no_grad(): # No necesitamos calcular gradientes para la evaluación
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy of the model on the 10000 test images: {100 * correct / total}%')
Accuracy of the model on the 10000 test images: 99.18%